﻿using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;
using System.Collections.ObjectModel;
using System.Windows.Shapes;

namespace Zhy.DigitaPlatform.Models
{
    /// <summary>
    /// 设备的一个组件模块
    /// </summary>
    public class DeviceItemModel : ObservableObject
    {
        #region 属性
        /// <summary>
        /// 通过委托方式获取组件集合
        /// </summary>
        public Func<List<DeviceItemModel>> Devices { get; set; }

        /// <summary>
        /// 组件编号-唯一标识-主键
        /// </summary>
        public string DeviceNum { get; set; }
        /// <summary>
        /// 标题名称-用来在推动时显示标题名称
        /// </summary>
        public string Header { get; set; }
        private bool _isSelected;
        /// <summary>
        /// 记录选中前Z轴层级
        /// </summary>
        int z_temp = 0;
        /// <summary>
        /// 组件选中竖向
        /// </summary>
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                Set(ref _isSelected, value);

                if (value)
                {
                    z_temp = this.Z;
                    this.Z = 999; // 将选中组件层级设为最大
                }
                else this.Z = z_temp; // 失去选中后恢复组件Z轴层级
            }
        }

        private bool _isVisible = true;
        /// <summary>
        /// 用于设置组件是否显示
        /// 主要用于控制对齐线是否显示。
        /// </summary>
        public bool IsVisible
        {
            get { return _isVisible; }
            set { Set(ref _isVisible, value); }
        }

        private double x;
        /// <summary>
        /// 组态控件X轴坐标
        /// </summary>
        public double X
        {
            get { return x; }
            set { Set(ref x, value); }
        }
        private double y;
        /// <summary>
        /// 组态控件Y轴坐标
        /// </summary>
        public double Y
        {
            get { return y; }
            set { Set(ref y, value); }
        }
        private int z = 0;
        /// <summary>
        /// 组态控件层级关系
        /// </summary>
        public int Z
        {
            get { return z; }
            set { Set(ref z, value); }
        }

        private double _width;
        /// <summary>
        /// 组态控件宽度
        /// </summary>
        public double Width
        {
            get { return _width; }
            set { Set(ref _width, value); }
        }
        private double _height;
        /// <summary>
        /// 组态控件高度
        /// </summary>
        public double Height
        {
            get { return _height; }
            set { Set(ref _height, value); }
        }

        private int _rotate;
        /// <summary>
        /// 设置组件的旋转角度
        /// </summary>
        public int Rotate
        {
            get { return _rotate; }
            set { Set(ref _rotate, value); }
        }

        private int _flowDirection;
        /// <summary>
        /// 设置管道组件的流向
        /// </summary>
        public int FlowDirection
        {
            get { return _flowDirection; }
            set { Set(ref _flowDirection, value); }
        }
        /// <summary>
        /// 报警状态
        /// </summary>
        private bool _isWarning;
        /// <summary>
        /// 报警状态
        /// </summary>
        public bool IsWarning
        {
            get { return _isWarning; }
            set { Set(ref _isWarning, value); }
        }
        /// <summary>
        /// 报警信息
        /// </summary>
        private string _warningMessage;
        /// <summary>
        /// 报警信息
        /// </summary>
        public string WarningMessage
        {
            get { return _warningMessage; }
            set { Set(ref _warningMessage, value); }
        }

        /// <summary>
        /// 根据这个名称动态创建一个组件实例
        /// </summary>
        public string DeviceType { get; set; }

        /// <summary>
        /// 子项删除命令
        /// </summary>
        public RelayCommand<DeviceItemModel> DeleteCommand { get; set; }
        /// <summary>
        /// 添加通讯参数属性命令
        /// </summary>
        public RelayCommand AddPropCommand { get; set; }
        /// <summary>
        /// 属性子项删除命令
        /// </summary>
        public RelayCommand<DevicePropModel> DeletePropCommand { get; set; }
        /// <summary>
        /// 点位配置添加命令
        /// </summary>
        public RelayCommand AddVariableCommand { get; set; }
        /// <summary>
        /// 点位配置属性删除命令
        /// </summary>
        public RelayCommand<VariableModel> DeleteVariableCommand { get; set; }

        #region 缩放动作命令
        /// <summary>
        /// 鼠标按钮命令
        /// </summary>
        public RelayCommand<object> ResizeDownCommand { get; set; }
        /// <summary>
        /// 鼠标移动命令
        /// </summary>
        public RelayCommand<object> ResizeMoveCommand { get; set; }
        /// <summary>
        /// 鼠标抬起命令
        /// </summary>
        public RelayCommand<object> ResizeUpCommand { get; set; }
        #endregion

        /// <summary>
        /// 通讯参数数据集合
        /// </summary>
        public ObservableCollection<DevicePropModel> PropList { get; set; } = new ObservableCollection<DevicePropModel>();
        /// <summary>
        /// 点位配置数据集合
        /// </summary>
        public ObservableCollection<VariableModel> VariableList { get; set; } = new ObservableCollection<VariableModel>();
        #endregion

        /// <summary>
        /// 初始化
        /// </summary>
        public DeviceItemModel()
        {
            // 实例化添加通讯参数命令
            AddPropCommand = new RelayCommand(() =>
            {
                PropList.Add(new DevicePropModel() { PropName = "Protocol", PropValue = "ModbusRtu" });
            });
            // 实例化属性删除命令
            DeletePropCommand = new RelayCommand<DevicePropModel>(model => PropList.Remove(model));

            // 点位配置属性添加命令
            AddVariableCommand = new RelayCommand(() =>
            {
                VariableList.Add(new VariableModel() { VarNum = DateTime.Now.ToString("yyyyMMddHHmmssFFF") });
            });
            // 点位配置属性删除命令
            DeleteVariableCommand = new RelayCommand<VariableModel>(model => VariableList.Remove(model));
            // 实例化处理组件尺寸缩放命令
            ResizeDownCommand = new RelayCommand<object>(DoResizeDown);
            ResizeMoveCommand = new RelayCommand<object>(DoResizeMove);
            ResizeUpCommand = new RelayCommand<object>(DoResizeUp);
        }

        /// <summary>
        /// 光标对于拖动对象的坐标值
        /// 光标的点在组件内，是在组件内计算的光标的坐标点
        /// </summary>
        Point startP = new Point(0, 0);
        #region 处理组件移动逻辑
        /// <summary>
        /// 鼠标按下标记标记
        /// 用于移动方法判断使用
        /// </summary>
        bool isMoving = false;
        /// <summary>
        /// 组件集合
        /// </summary>
        List<DeviceItemModel> devicesTemp = new List<DeviceItemModel>();
        /// <summary>
        /// 辅助对齐线集合
        /// </summary>
        List<DeviceItemModel> lineTemp = new List<DeviceItemModel>();

        /// <summary>
        /// 组件项鼠标左键按下事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // 获取光标对于拖动对象的坐标值
            startP = e.GetPosition((System.Windows.IInputElement)sender);
            isMoving = true;

            // 获取所有设备的列表 - 当点击组件时这两个集合会被初始化好
            // 获取非当前和非辅助线的组件
            devicesTemp = Devices().Where(d => !new string[] { "HL", "VL", "WidthRule", "HeightRule" }.Contains(d.DeviceType) && d != this).ToList();
            // 获取宽高辅助线组件
            lineTemp = Devices().Where(d => new string[] { "HL", "VL" }.Contains(d.DeviceType)).ToList();

            // 鼠标光标捕获
            Mouse.Capture((IInputElement)sender);
            // 如果依赖属性的参数没有生效，有可能是因为她引起的。
            e.Handled = true; // 用于阻断路由事件的冒泡排序
        }

        /// <summary>
        /// 组件项鼠标移动事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnMouseMove(object sender, MouseEventArgs e)
        {
            // 移动 是在按下 之后
            if (isMoving)
            {
                // 计算出新的位置
                // 相对的是Canvas画布
                // 可以通过视觉树查找  
                // 这个坐标是组件编辑区域光标的坐标点
                Point p = e.GetPosition(GetParent((FrameworkElement)sender));

                double _x = p.X - startP.X; // 所拖动组件的左上角X轴坐标
                double _y = p.Y - startP.Y; // 所拖动组件的左上角Y轴坐标
                // 处理X方向的对齐（纵线）
                {
                    // 计算组件与当前组件之间的X距离
                    double minDistance = devicesTemp.Min(d => Math.Abs(d.X - _x)); // 计算x轴相差最小的值
                    var line = lineTemp.First(l => l.DeviceType == "VL"); // 获取垂直辅助线

                    if (minDistance < 20) // 最小值小于预定值
                    {
                        // 距离在20以内，显示对齐线 
                        // 获取最小差值组件
                        var dim = devicesTemp.FirstOrDefault(d => Math.Abs(Math.Abs(d.X - _x) - minDistance) < 1);
                        if (dim != null)
                        {
                            line.IsVisible = true; // 设置显示垂直辅助线
                            line.X = dim.X; // 将线的X坐标设置成要对齐的组件x坐标

                            if (minDistance < 10)
                                _x = dim.X; // 这里是为了做一个吸附功能，让组件自动与相近的组件对齐
                        }
                    }
                    else
                        line.IsVisible = false; // 设置组件大于20像素后隐藏辅助线
                }

                // 处理Y方向的对齐（横线）
                {
                    // 获取最小差值组件
                    double minDistance = devicesTemp.Min(d => Math.Abs(d.Y - _y));
                    var line = lineTemp.First(l => l.DeviceType == "HL"); // 获取水平辅助线
                    if (minDistance < 20)
                    {
                        // 距离在20以内，显示对齐线
                        // 获取最小差值组件
                        var dim = devicesTemp.FirstOrDefault(d => Math.Abs(Math.Abs(d.Y - _y) - minDistance) < 1);
                        if (dim != null)
                        {
                            line.IsVisible = true; // 设置显示垂直辅助线
                            line.Y = dim.Y; // 将线的X坐标设置成要对齐的组件x坐标

                            if (minDistance < 10)
                                _y = dim.Y; // 这里是为了做一个吸附功能，让组件自动与相近的组件对齐
                        }
                    }
                    else
                        line.IsVisible = false; // 设置组件大于20像素后隐藏辅助线
                }

                // 数据驱动   通过数据模型中的属性变化 ，驱使页面对象的呈现改变
                X = _x; // 将记录的吸附坐标赋值给拖动坐标，让组件自动对齐剩下的10像素。
                Y = _y; // 将记录的吸附坐标赋值给拖动坐标，让组件自动对齐剩下的10像素。
            }
        }

        /// <summary>
        /// 组件项鼠标释放事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            // 鼠标选中组件结束
            isMoving = false;
            foreach (var item in lineTemp)
            {  // 鼠标抬起的时候隐藏对齐辅助线
                item.IsVisible = false;
            }
            // 释放光标
            Mouse.Capture(null);
        }

        /// <summary>
        /// 根据视觉树获取组件对象
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        private Canvas GetParent(FrameworkElement d)
        {
            dynamic obj = VisualTreeHelper.GetParent(d);
            if (obj != null && obj is Canvas)
                return obj;

            return GetParent(obj);
        }
        #endregion

        #region 处理组件尺寸缩放逻辑
        /// <summary>
        /// 组件选中标识-用于组件是否正在拖动
        /// </summary>
        bool _isResize = false;
        /// <summary>
        /// 组件拖动缩放前的宽度与高度值
        /// </summary>
        double oldWidth, oldHeight;
        /// <summary>
        /// 宽度比对标识
        /// </summary>
        DeviceItemModel WR;
        DeviceItemModel HR;
        /// <summary>
        /// 组件鼠标按下命令
        /// </summary>
        /// <param name="obj"></param>
        private void DoResizeDown(object obj)
        {
            var e = obj as MouseButtonEventArgs; // 获取事件参数
            _isResize = true;
            startP = e.GetPosition(GetParent((FrameworkElement)e.Source));

            // 获取所有可做比对的其他组件
            devicesTemp = Devices().Where(d => !new string[] { "HL", "VL", "WidthRule", "HeightRule" }.Contains(d.DeviceType) && d != this).ToList();
            // 鼠标按下时记录 缩放拖动前的宽度与高度值
            oldWidth = this.Width;
            oldHeight = this.Height;

            // 获取相对Canvas的按下坐标  - 鼠标捕获组件元素-用于拖动组件时判断元素类型
            Mouse.Capture((IInputElement)e.Source);
            e.Handled = true; // 用于阻断路由事件的冒泡排序
        }
        /// <summary>
        /// 组件鼠标移动命令
        /// </summary>
        /// <param name="obj"></param>
        private void DoResizeMove(object obj)
        {
            var e = obj as MouseEventArgs; // 获取事件参数
            ReleaseRule();
            if (_isResize) // 组件选中正在拖动
            {
                // 获取相对于Canvas的鼠标位置
                Point current = e.GetPosition(GetParent((FrameworkElement)e.Source));
                // 根据光标类型判断是如何变化 
                var c = (e.Source as Ellipse).Cursor;
                if (c != null)
                {
                    if (c == Cursors.SizeWE)// 水平方向
                    {
                        if (Keyboard.Modifiers == ModifierKeys.Alt)  // 移动过程中检查Alt按下，不做对齐
                            this.Width = oldWidth + current.X - startP.X; // 任意拖动不显示宽度对齐线
                        else
                            this.Width = ShowWidthRule(oldWidth + current.X - startP.X);
                    }
                    else if (c == Cursors.SizeNS)// 垂直方向
                    {
                        if (Keyboard.Modifiers == ModifierKeys.Alt)
                            this.Height = oldHeight + current.Y - startP.Y; // 任意拖动不显示宽度对齐线
                        else
                            this.Height = ShowHeightRule(oldHeight + current.Y - startP.Y);
                    }
                    else if (c == Cursors.SizeNWSE)// 右下方向
                    {
                        if (Keyboard.Modifiers == ModifierKeys.Shift)
                        {   // 任意拖动不显示宽度对齐线
                            // 锁定比例（如果拖动的时候同时按下了Ctrl键 ）
                            var rate = this.Width / this.Height;
                            this.Width = ShowWidthRule(oldWidth + current.X - startP.X);
                            this.Height = this.Width / rate;
                        }
                        else
                        {
                            // 没有锁定
                            this.Width = ShowWidthRule(oldWidth + current.X - startP.X);
                            this.Height = ShowHeightRule(oldHeight + current.Y - startP.Y);
                        }
                    }
                }
                e.Handled = true; // 用于阻断路由事件的冒泡排序
            }
        }
        /// <summary>
        /// 组件鼠标抬起命令
        /// </summary>
        /// <param name="obj"></param>
        private void DoResizeUp(object obj)
        {
            _isResize = false; // 鼠标松开拖动完成
            var e = obj as MouseButtonEventArgs; // 获取事件参数
            e.Handled = true; // 用于阻断路由事件的冒泡排序
            Mouse.Capture(null); // 释放鼠标捕获元素
            ReleaseRule();
        }
        /// <summary>
        /// 释放宽度和高度对齐线组件
        /// </summary>
        private void ReleaseRule()
        {
            if (WR != null)
            {
                WR.IsVisible = false;
                WR = null;
            }
            if (HR != null)
            {
                HR.IsVisible = false;
                HR = null;
            }
        }
        /// <summary>
        /// 计算并显示宽度对齐线
        /// </summary>
        /// <param name="width"></param>
        /// <returns></returns>
        private double ShowWidthRule(double width)
        {
            if (WR == null)
                WR = Devices().FirstOrDefault(d => d.DeviceType == "WidthRule"); // 在当前组件集合中找到宽度对齐组件
            // 在当前组件集合中找到宽度绝对值小于20个像素的对象
            var dim = devicesTemp.FirstOrDefault(d => Math.Abs(d.Width - width) < 20);
            if (dim != null)
            {
                WR.IsVisible = true; // 显示宽度对齐组件
                WR.Width = dim.Width;
                WR.Y = dim.Y + dim.Height + 5;
                WR.X = dim.X;

                if (Math.Abs(dim.Width - width) < 5)
                    width = dim.Width; // 这里是为了做一个吸附功能，让组件自动调整成相近组件大小
            }
            else WR.IsVisible = false; // 隐藏对齐的宽度吸附组件

            return width; // 返回宽度
        }
        /// <summary>
        /// 计算并显示高度对齐线
        /// </summary>
        /// <param name="heigth"></param>
        /// <returns></returns>
        private double ShowHeightRule(double heigth)
        {
            if (HR == null)
                HR = Devices().FirstOrDefault(d => d.DeviceType == "HeightRule"); // 在当前组件集合中找到宽度对齐组件
            // 在当前组件集合中找到宽度绝对值小于20个像素的对象
            var dim = devicesTemp.FirstOrDefault(d => Math.Abs(d.Height - heigth) < 20);
            if (dim != null)
            {
                HR.IsVisible = true; // 显示宽度对齐组件
                HR.Height = dim.Height;
                HR.Y = dim.Y;
                HR.X = dim.X + dim.Width + 5;

                if (dim.Height - heigth < 5)
                    heigth = dim.Height; // 这里是为了做一个吸附功能，让组件自动调整成相近组件大小
            }
            else HR.IsVisible = false; // 隐藏对齐的宽度吸附组件

            return heigth; // 返回宽度
        }
        #endregion

        #region 初始化右键菜单
        /// <summary>
        /// 右键菜单集合
        /// </summary>
        public List<Control> ContextMenus { get; set; }
        /// <summary>
        /// 右键菜单初始化
        /// </summary>
        public void InitContextMenu()
        {
            ContextMenus = new List<Control>();
            ContextMenus.Add(new MenuItem
            {
                Header = "顺时针旋转",
                Command = new RelayCommand(() => this.Rotate += 90),
                // 设置右键菜单是否显示顺时针旋转项
                Visibility = new string[] {
                    "RAJoints", "TeeJoints","Temperature","Humidity","Pressure","Flow","Speed"
                }.Contains(this.DeviceType) ? Visibility.Visible : Visibility.Collapsed
            });
            ContextMenus.Add(new MenuItem
            {
                Header = "逆时针旋转",
                Command = new RelayCommand(() => this.Rotate -= 90),
                // 设置右键菜单是否显示逆时针旋转项
                Visibility = new string[] {
                    "RAJoints", "TeeJoints","Temperature","Humidity","Pressure","Flow","Speed"
                }.Contains(this.DeviceType) ? Visibility.Visible : Visibility.Collapsed
            });
            ContextMenus.Add(new MenuItem
            {
                Header = "改变流向",
                Command = new RelayCommand(() => this.FlowDirection = (++this.FlowDirection) % 2),
                // 设置右键菜单是否显示改变流向
                Visibility = new string[] { "HorizontalPipeline", "VerticalPipeline" }.Contains(this.DeviceType) ? Visibility.Visible : Visibility.Collapsed
            });

            ContextMenus.Add(new Separator());

            ContextMenus.Add(new MenuItem
            {
                Header = "向上一层",
                Command = new RelayCommand(() => this.Z++)
            });
            ContextMenus.Add(new MenuItem
            {
                Header = "向下一层",
                Command = new RelayCommand(() => this.Z--)
            });
            ContextMenus.Add(new Separator { });

            ContextMenus.Add(new MenuItem
            {
                Header = "删除",
                Command = this.DeleteCommand,
                CommandParameter = this
            });

            var cms = ContextMenus.Where(cm => cm.Visibility == Visibility.Visible).ToList();
            foreach (var item in cms)
            {   // 判断如果没有个性话菜单时
                if (item is Separator)
                    item.Visibility = Visibility.Collapsed;
                else break;
            }

        }
        #endregion
    }
}
